package genesis

import (
	"context"
	"fmt"
	"os"

	"github.com/OffchainLabs/prysm/v7/api/client"
	"github.com/OffchainLabs/prysm/v7/api/client/beacon"
	"github.com/OffchainLabs/prysm/v7/beacon-chain/state"
	"github.com/OffchainLabs/prysm/v7/encoding/ssz/detect"
	"github.com/pkg/errors"
)

// Provider is a type that can provide the genesis state for the initialization of the genesis package.
// Examples are getting the state from a beacon node API, reading it from a file, or from the legacy database.
type Provider interface {
	Genesis(context.Context) (state.BeaconState, error)
}

var _ Provider = &FileProvider{}
var _ Provider = &APIProvider{}

// APIProvider provides a genesis state using the given beacon node API url.
type APIProvider struct {
	c *beacon.Client
}

// NewAPIProvider creates an APIProvider, handling the set up of a beacon node api client.
func NewAPIProvider(beaconNodeHost string) (*APIProvider, error) {
	c, err := beacon.NewClient(beaconNodeHost, client.WithMaxBodySize(client.MaxBodySizeState))
	if err != nil {
		return nil, errors.Wrapf(err, "unable to parse beacon node url or hostname - %s", beaconNodeHost)
	}
	return &APIProvider{c: c}, nil
}

// Genesis satisfies the Provider interface by retrieving the genesis state from the beacon node API and unmarshaling it into a phase0 beacon state.
func (dl *APIProvider) Genesis(ctx context.Context) (state.BeaconState, error) {
	sb, err := dl.c.GetState(ctx, beacon.IdGenesis)
	if err != nil {
		return nil, err
	}
	return detect.UnmarshalState(sb)
}

// FileProvider provides the genesis state by reading the given ssz-encoded beacon state file path.
type FileProvider struct {
	statePath string
}

// NewFileProvider validates the given path information and creates a Provider which sources
// the genesis state from an ssz-encoded file on the local filesystem.
func NewFileProvider(statePath string) (*FileProvider, error) {
	var err error
	if err = existsAndIsFile(statePath); err != nil {
		return nil, err
	}
	// stat just to make sure it actually exists and is a file
	return &FileProvider{statePath: statePath}, nil
}

// Genesis satisfies the Provider interface by reading the genesis state from a file and unmarshaling it.
func (fi *FileProvider) Genesis(_ context.Context) (state.BeaconState, error) {
	return stateFromFile(fi.statePath)
}

func existsAndIsFile(path string) error {
	info, err := os.Stat(path)
	if err != nil {
		return errors.Wrapf(err, "error checking existence of ssz-encoded file %s for genesis state init", path)
	}
	if info.IsDir() {
		return fmt.Errorf("%s is a directory, please specify full path to file", path)
	}
	return nil
}
